#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>


#define NUM 1024
#define OPT_NUM 64

#define NONE_REDIR 0
#define INPUT_REDIR 1 //标准输入>
#define OUTPUT_REDIR 2 //标准输出<
#define APPEND_REDIR 3 //追加>>

#define trimSpace(start) do{\
   while(isspace(*start)) start++;\
}while(0)

//#define DEBUG 
char lineCommand[NUM];
char* myargv[OPT_NUM];//指针数组:用于存放你在命令行中写的命令(切割后的)
int lastCode = 0;//错误码
int lastSig = 0;//异常信号

int redirType = NONE_REDIR;//初始什么都不做
char *redirFile = NULL;

void commandCheck(char* commands){
  assert(commands);
  char* start = commands;
  char* end = commands + strlen(commands);
  
  while(start < end){
    if(*start == '>'){//重定向输出
      *start = '\0';
      start++;
      if(*start == '>'){//重定向追加
            redirType = APPEND_REDIR;
            start++;
      }else{
        redirType = OUTPUT_REDIR;
      }
    trimSpace(start);
      redirFile = start;
      break;
    
    }else if(*start == '<'){//重定向输入
        *start = '\0';
        start++;
        trimSpace(start); //去掉失误输入的多个空格
        redirType = INPUT_REDIR;
        redirFile = start; //这里的文件表示，切割后0后面的必然是文件的名称
        break;
    }else{
      start++;
    }
        
    

  }

}

int main(){
  //输出提示符
  //每一次输入命令都初始化
  while(1){
  redirType = NONE_REDIR;
  redirFile = NULL;
  errno = 0;
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);

  char* s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);
  assert(s!=NULL);
  (void)s;
  //清除缓冲区中，用户输入的\n
  lineCommand[strlen(lineCommand)-1] = 0;
  
  //字符串切割
  //”ls -a -l“->"ls" "-a" "-l"
  //"ls -a -l > log.txt" -> "ls -a -l" > "log.txt"
  //这上面的意思是将ls显示的路径输出到log文件中，也就是重定向输出
  //"cat < log.txt" -> "cat" < "log.txt" 这里是重定向了输入
  
  commandCheck(lineCommand);//将输入的命令进行切割，分成3部分
  myargv[0] = strtok(lineCommand," ");//根据”“做切割
  int i=1;
  //myargv[1] = strtok(NULL," ");
  while(myargv[i++] = strtok(NULL," "));//第二次切割原生的字符串，要传NULL ，切割好的字符串都放到myargv中
  
  //为啥我们自己写的shell，cd的时候，路径没有发生变化呢？
  //fork创建了子进程，实际上是子进程去执行的cd，更改的就是子进程的路径
  //但是子进程结束后，继续用的是父进程，即shell，所以看不到变化
  
  //如果是cd命令，我们就不创建子进程，让shell自己执行，本质就是执行系统接口
  //像这种不需要我们的子进程来执行，而是让shell自己执行的命令，就称为--内建/内置命令
  if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0){
    if(myargv[1] != NULL){
      chdir(myargv[1]);  //cd去哪个路径，那就把工作目录修改为那个路径
    }
    continue;
  }


  //echo的实现
  if(myargv[0]!=NULL && myargv[1]!=NULL && strcmp(myargv[0],"echo") == 0 ){
    if(strcmp(myargv[1],"$?") == 0){
      printf("%d,%d\n",lastCode,lastSig);
    }else{
      printf("%s\n",myargv[1]);
    }
    continue;

  }




  //测试
#ifdef DEBUG 
  for(int i=0;myargv[i];i++){
    printf("myargv[%d]:%s\n",i,myargv[i]);
  }
#endif


  
  pid_t id = fork();//让子进程去进行程序替换，也就是让子进程去执行命令
    
  assert(id !=-1);
  if(id == 0){
    //因为这些命令是子进程执行的，真正重定向的工作一定要子进程来完成
    //如何重定向，是父进程要给子进程提供的信息
    //这里会影响父进程吗？不会，因为进程具有独立性，会发生写时拷贝
    switch(redirType){  
      case NONE_REDIR:
        //什么都不做
        break;
      case INPUT_REDIR:
        {
        int fd = open(redirFile,OUTPUT_REDIR);
        if(fd < 0){
          perror("open");
          exit(errno);
        }
        //重定向的文件已经打开了
        dup2(fd,0);
       }
        break;
      case OUTPUT_REDIR:
      case APPEND_REDIR:
        {
          umask(0);
          int flags = O_WRONLY | O_CREAT;
          if(redirType == APPEND_REDIR) flags |= O_APPEND; 
          else flags |= O_TRUNC;

          int fd = open(redirFile,flags,0666);
          if(fd < 0){
            perror("open");
            exit(errno);
          }
          dup2(fd,1);
        }
        break;
      default:
        printf("bug?\n");
        break;
    }



    execvp(myargv[0],myargv);
    exit(1);
  }
  int status = 0;
  pid_t ret = waitpid(id,&status,0);
  assert(ret > 0);
  (void)ret;
  lastCode = (status>>8)&0xFF;
  lastSig = status & 0x7F;

  }
  return 0;
}

  
